package ch.parren.jdepextract;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;

import ch.parren.java.lang.New;
import ch.parren.jdepchk.classes.ClassSet;
import ch.parren.jdepchk.classes.ClassSets;
import ch.parren.jdepchk.classes.ClassesDirClassSet;
import ch.parren.jdepchk.classes.JarFileClassSet;
import ch.parren.jdepchk.classes.JarsDirClassSet;
import ch.parren.jdepchk.classes.SingleClassSet;
import ch.parren.jdepchk.config.ConfigParser;

public class JDepExtract {

	public static void main(String[] args) throws Exception {
		final Collection<Config> cfgs = New.arrayList();
		boolean showStats = false;
		int nMaxJobs = Runtime.getRuntime().availableProcessors() * 2;

		{
			final Config cfg = new Config();
			int i = 0;
			while (i < args.length) {
				final String arg = args[i++];
				if ("--config".equals(arg) || "-f".equals(arg)) {
					parseConfig(new File(args[i++]), cfgs);
				} else if ("--internal".equals(arg) || "-i".equals(arg)) {
					cfg.internalDir = new File(args[i++]);
				} else if ("--external".equals(arg) || "-e".equals(arg)) {
					cfg.externalDir = new File(args[i++]);
				} else if ("--classes".equals(arg) || "-c".equals(arg)) {
					final File f = new File(args[i++]);
					if (f.isDirectory())
						cfg.classSets.add(new SingleClassSet(new ClassesDirClassSet(f)));
					else
						System.err.println("WARNING: Ignoring --classes " + f);
				} else if ("--jars".equals(arg) || "--jar".equals(arg) || "-j".equals(arg)) {
					final File f = new File(args[i++]);
					if (f.isDirectory())
						cfg.classSets.add(new JarsDirClassSet(true, f));
					else if (f.isFile())
						cfg.classSets.add(new SingleClassSet(new JarFileClassSet(f)));
					else
						System.err.println("WARNING: Ignoring --jar(s) " + f);
				} else if ("--jobs".equals(arg)) {
					nMaxJobs = Integer.parseInt(args[i++]);
				} else if ("--show-stats".equals(arg)) {
					showStats = true;
				} else if ("--help".equals(arg) || "-h".equals(arg)) {
					showHelp();
					return;
				} else {
					System.out.println("ERROR: Invalid command line argument: " + arg);
					System.out.println("Use --help to see help.");
					System.exit(2);
				}
			}

			if (!cfg.classSets.isEmpty())
				cfgs.add(cfg);
		}

		boolean hadChanges = false;

		final long before = System.currentTimeMillis();
		for (final Config cfg : cfgs) {

			final boolean isConcurrent = (nMaxJobs > 1);
			final RuleFilesManager mgr = new RuleFilesManager(cfg.internalDir, cfg.externalDir, "", isConcurrent);

			if (!isConcurrent) {
				final Extractor extractor = new Extractor(mgr);
				for (ClassSets classSets : cfg.classSets) {
					classSets.accept(new ClassSets.Visitor() {
						public void visitClassSet(ClassSet classSet) throws IOException {
							extractor.extractFrom(classSet);
						}
					});
				}
			} else {
				final ConcurrentLinkedQueue<ClassSet> sets = new ConcurrentLinkedQueue<ClassSet>();
				for (ClassSets classSets : cfg.classSets) {
					classSets.accept(new ClassSets.Visitor() {
						public void visitClassSet(ClassSet classSet) throws IOException {
							sets.add(classSet);
						}
					});
				}
				final int nJobs = Math.min(nMaxJobs, sets.size());
				final Thread[] jobs = new Thread[nJobs];
				for (int i = 0; i < nJobs; i++) {
					jobs[i] = new Thread() {
						@Override public void run() {
							final Extractor extractor = new Extractor(mgr);
							while (true) {
								final ClassSet set = sets.poll();
								if (null == set)
									break;
								try {
									extractor.extractFrom(set);
								} catch (IOException e) {
									e.printStackTrace();
									throw new RuntimeException(e);
								}
							}
						}
					};
					jobs[i].start();
				}
				for (int i = 0; i < nJobs; i++) {
					jobs[i].join();
				}
			}

			hadChanges |= mgr.finish();
		}

		final long after = System.currentTimeMillis();
		final long taken = after - before;

		if (showStats) {
			System.out.println();
			System.out.println(taken + " ms taken.");
		}

		if (hadChanges)
			System.exit(1);

	}

	private static final class Config {
		final Collection<ClassSets> classSets = New.linkedList();
		File internalDir;
		File externalDir;
	}

	private static void parseConfig(File configFile, final Collection<Config> configs) throws Exception {
		new ConfigParser<Exception>(new ConfigParser.Visitor<Exception>() {
			Config scope = null;
			String ruleName = null;
			String firstRule = null;
			@Override protected void visitClassSpecsStart() throws IOException, Exception {
				scope = new Config();
			}
			@Override protected void visitClassSpec(String spec) throws IOException, Exception {
				scope.classSets.add(new SingleClassSet(new ClassesDirClassSet(new File(spec))));
			}
			@Override protected void visitRuleSpecsStart(String name) throws IOException, Exception {
				ruleName = name;
				firstRule = null;
			}

			@Override protected void visitRuleSpec(String spec) throws IOException, Exception {
				if (null == firstRule)
					firstRule = spec;
			}
			@Override protected void visitRuleSpecsEnd() throws IOException, Exception {
				if ("internal".equals(ruleName))
					scope.internalDir = new File(firstRule);
				else if ("external".equals(ruleName))
					scope.externalDir = new File(firstRule);
			}
			@Override protected void visitClassSpecsEnd() throws IOException, Exception {
				configs.add(scope);
				scope = null;
			}
			@Override protected void visitError(String message) throws IOException, Exception {
				throw new Exception(message);
			}
		}).parseConfig(configFile);
	}

	private static void showHelp() throws IOException {
		final Reader r = new InputStreamReader(JDepExtract.class.getResourceAsStream("help.txt"), "UTF-8");
		try {
			final char[] buf = new char[1024];
			int red;
			while ((red = r.read(buf)) > 0)
				if (red < buf.length) {
					final char[] part = new char[red];
					System.arraycopy(buf, 0, part, 0, red);
					System.out.print(part);
				} else {
					System.out.print(buf);
				}
		} finally {
			r.close();
		}
	}

}
